﻿namespace SupportSystemUI.Controls.Utility
{
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media.Animation;

    /// <summary>
    /// Indicates the direction of a SlidePanel object.
    /// </summary>
    public enum SlidingDirection
    {
        /// <summary>
        /// Indicates a vertical sliding direction.
        /// </summary>
        Vertical,

        /// <summary>
        /// Indicates a horizontal sliding direction.
        /// </summary>
        Horizontal,
    }

    /// <summary>
    /// Interaction logic for SlidePanel.xaml
    /// </summary>
    public partial class SlidePanel : UserControl
    {
        /// <summary>
        /// The dependency property for the main slide the panel starts with.
        /// </summary>
        public static readonly DependencyProperty MainSlideProperty = DependencyProperty.Register(
            "MainSlide", typeof(UIElement), typeof(SlidePanel), new UIPropertyMetadata(null));

        /// <summary>
        /// The dependency property for the direction the SlidePanel moves in.
        /// </summary>
        public static readonly DependencyProperty SlidingDirectionProperty = DependencyProperty.Register(
            "SlidingDirection", typeof(SlidingDirection), typeof(SlidePanel), new UIPropertyMetadata(SlidingDirection.Horizontal));

        /// <summary>
        /// The index of the current slide in the grid.
        /// </summary>
        private int currentSlide = 0;

        /// <summary>
        /// Initializes a new instance of the <see cref="SlidePanel"/> class.
        /// </summary>
        public SlidePanel()
        {
            this.InitializeComponent();
            this.Loaded += (sender, args) =>
            {
                if (this.MainSlide != null)
                {
                    this.AddSlide(this.MainSlide);
                }
            };
        }

        /// <summary>
        /// Gets or sets the dependency property for the direction the SlidePanel slides in.
        /// </summary>
        public SlidingDirection SlidingDirection
        {
            get { return (SlidingDirection)this.GetValue(SlidingDirectionProperty); }
            set { this.SetValue(SlidingDirectionProperty, value); }
        }

        /// <summary>
        /// Gets or sets the dependency property for the main slide the panel starts with.
        /// </summary>
        public UIElement MainSlide
        {
            get { return (UIElement)this.GetValue(MainSlideProperty); }
            set { this.SetValue(MainSlideProperty, value); }
        }

        /// <summary>
        /// Gets the number of slides inside this control.
        /// </summary>
        public int SlidesCount
        {
            get { return this.mainGrid.Children.Count; }
        }

        /// <summary>
        /// Animates the view to scroll to the next slide.
        /// </summary>
        /// <param name="slide">Content of the new slide.</param>
        public void Push(UIElement slide)
        {
            this.AddSlide(slide);

            if (this.currentSlide < this.mainGrid.Children.Count - 1)
            {
                Thickness thickness = this.SlidingDirection == SlidingDirection.Horizontal
                    ? new Thickness((-this.currentSlide - 1) * this.ActualWidth, 0, 0, 0)
                    : new Thickness(0, (-this.currentSlide - 1) * this.ActualHeight, 0, 0);

                ThicknessAnimation anim = new ThicknessAnimation(thickness, TimeSpan.FromMilliseconds(400));
                anim.AccelerationRatio = anim.DecelerationRatio = 0.4;
                this.mainGrid.BeginAnimation(Grid.MarginProperty, anim);

                this.currentSlide++;
            }
        }

        /// <summary>
        /// Animates the view to scroll to the previous slide.
        /// </summary>
        /// <returns>True if a slide was popped, false otherwise.</returns>
        public bool Pop()
        {
            if (this.currentSlide > 0)
            {
                Thickness thickness = this.SlidingDirection == SlidingDirection.Horizontal
                    ? new Thickness((-this.currentSlide + 1) * this.ActualWidth, 0, 0, 0)
                    : new Thickness(0, (-this.currentSlide + 1) * this.ActualHeight, 0, 0);

                ThicknessAnimation anim = new ThicknessAnimation(thickness, TimeSpan.FromMilliseconds(400));
                anim.AccelerationRatio = anim.DecelerationRatio = 0.4;
                anim.Completed += (sender, args) => { this.RemoveSlide(); };
                this.mainGrid.BeginAnimation(Grid.MarginProperty, anim);

                this.currentSlide--;
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Removes all slides from panel.
        /// </summary>
        public void Clear()
        {
            while (this.currentSlide >= 0)
            {
                this.currentSlide--;
                this.RemoveSlide();
            }

            this.mainGrid.SetValue(Grid.MarginProperty, new Thickness());
        }

        /// <summary>
        /// Adds a slide to the current slides stack, and can navigate to the new slide.
        /// </summary>
        /// <param name="slide">Content of the new slide.</param>
        private void AddSlide(UIElement slide)
        {
            if (this.SlidingDirection == SlidingDirection.Horizontal)
            {
                ColumnDefinition column = new ColumnDefinition();
                column.Width = new GridLength(this.ActualWidth);
                this.mainGrid.ColumnDefinitions.Add(column);

                Grid.SetRow(slide, 0);
                Grid.SetColumn(slide, this.mainGrid.ColumnDefinitions.Count - 1);
            }
            else if (this.SlidingDirection == SlidingDirection.Vertical)
            {
                RowDefinition row = new RowDefinition();
                row.Height = new GridLength(this.ActualHeight);
                this.mainGrid.RowDefinitions.Add(row);

                Grid.SetRow(slide, this.mainGrid.RowDefinitions.Count - 1);
                Grid.SetColumn(slide, 0);
            }

            this.mainGrid.Children.Add(slide);
        }

        /// <summary>
        /// Removes the topmost slide from the collection and returns it.
        /// </summary>
        private void RemoveSlide()
        {
            if (this.mainGrid.Children.Count > 0)
            {
                this.mainGrid.Children.RemoveAt(this.mainGrid.Children.Count - 1);
                if (this.SlidingDirection == SlidingDirection.Horizontal)
                {
                    this.mainGrid.ColumnDefinitions.RemoveAt(this.mainGrid.ColumnDefinitions.Count - 1);
                }
                else if (this.SlidingDirection == SlidingDirection.Vertical)
                {
                    this.mainGrid.RowDefinitions.RemoveAt(this.mainGrid.RowDefinitions.Count - 1);
                }
            }
        }
    }
}
